package com.se.hospitalmonitoring;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.time.Second;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;

import com.se.hospitalmonitoring.VitalSensorInterface.MockVitalSensor;
import com.se.hospitalmonitoring.VitalSensorInterface.VitalAlarmListener;
import com.se.hospitalmonitoring.VitalSensorInterface.VitalSensorListener;
import com.se.hospitalmonitoring.VitalSign.VitalSignMeasurement;

public class BedSideInterface extends JFrame implements VitalSensorListener,
	VitalAlarmListener {

	private static final long serialVersionUID = 1L;

	private final JPanel contentPane;

	private JButton alarmButton = null;
	private BlinkyThread blinkerThread = null;
	private JFreeChart chart = null;

	private final TimeSeriesCollection dataset = new TimeSeriesCollection();
	private final HashMap<Long, TimeSeries> measurementSeries = new HashMap<Long, TimeSeries>();

	ArrayList<CancelCallListener> listeners = new ArrayList<CancelCallListener>();

	HashMap<Long, JComponent[]> measurementLabels = new HashMap<Long, JComponent[]>();
	private final ArrayList<VitalSign> disabledVitalSigns = new ArrayList<VitalSign>();

	/**
	 * Launches the GUI only
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					BedSideInterface frame = new BedSideInterface();
					frame.setVisible(true);
				}
				catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the frame.
	 */
	public BedSideInterface() {
		VitalSensorInterface.getInstance().addVitalAlarmListener(this);
		VitalSensorInterface.getInstance().addVitalSensorListener(this);
		setTitle("Bed Side Monitor");

		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 800, 600);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(new BorderLayout());

		JPanel panel = new JPanel();
		contentPane.add(panel, BorderLayout.NORTH);

		JPanel panel_1 = new JPanel();
		contentPane.add(panel_1, BorderLayout.EAST);
		panel_1.setLayout(new BorderLayout(0, 0));

		final JButton callButton = new JButton("Call Nurse");
		callButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				if (callButton.getText().equals("Call Nurse")) {
					callButton.setText("Dismiss Call");
					CallButtonInterface.getInstance().onCallButtonPressed();
				}
				else {
					callButton.setText("Call Nurse");
					for (CancelCallListener listener : listeners)
						listener.onCallCanceled();
				}
			}
		});
		JButton dismissAlarmButton = new JButton("Dismiss Alarm");
		dismissAlarmButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				blinkerThread.stopGracefully();
				blinkerThread = null;
				for (CancelCallListener listener : listeners)
					listener.onAlarmCanceled();
			}
		});
		JPanel callAlarmButtons = new JPanel(new BorderLayout());
		callAlarmButtons.add(callButton, BorderLayout.SOUTH);
		callAlarmButtons.add(dismissAlarmButton, BorderLayout.CENTER);
		panel_1.add(callAlarmButtons, BorderLayout.SOUTH);

		alarmButton = new JButton("Vital Sign Alarm!");
		alarmButton.setForeground(Color.BLACK);
		alarmButton.setBackground(Color.LIGHT_GRAY);
		panel_1.add(alarmButton, BorderLayout.CENTER);

		chart = ChartFactory.createTimeSeriesChart("Patient Vitals", "Time",
			"Measurement Percent", dataset, true, true, false);

		chart.setBackgroundPaint(Color.LIGHT_GRAY);
		final XYPlot plot = chart.getXYPlot();
		ValueAxis domain = plot.getDomainAxis();
		domain.setAutoRange(true);
		ValueAxis range = plot.getRangeAxis();
		range.setRange(0, 100);
		ChartPanel chartPanel = new ChartPanel(chart);
		chartPanel.setBackground(Color.LIGHT_GRAY);
		contentPane.add(chartPanel, BorderLayout.CENTER);

		for (final VitalSign vs : MockVitalSensor.VITAL_SIGNS) {
			TimeSeries ts = new TimeSeries(vs.name);
			ts.setMaximumItemCount(20);
			measurementSeries.put(vs.id, ts);
			dataset.addSeries(ts);
			JPanel measurementPanel = new JPanel();
			measurementPanel.setBorder(new LineBorder(Color.BLACK, 1, true));
			measurementPanel.setLayout(new GridLayout(2, 1, 0, 0));
			JPanel row1 = new JPanel();
			row1.setBorder(new EmptyBorder(3, 3, 3, 3));
			row1.setLayout(new BorderLayout(5, 5));

			JLabel title = new JLabel(vs.name);
			title.setFont(new Font("measureTitle", Font.PLAIN, 16));
			row1.add(title, BorderLayout.WEST);
			JButton rangeButton = new JButton("Range");

			rangeButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					final JDialog rangeDialog = new JDialog(
						BedSideInterface.this);
					rangeDialog.setTitle("Set vital sign range");
					rangeDialog.setSize(400, 175);
					rangeDialog.setLocationRelativeTo(BedSideInterface.this);
					JLabel diaText = new JLabel(
						"<html>Set the safe range for "
							+ vs.name
							+ ". Values are between 0% and 100%. Measurements"
							+ " that fall outside this range will trigger an alarm.</html>");
					diaText.setBorder(new EmptyBorder(0, 15, 0, 15));
					JPanel diaFields = new JPanel(new FlowLayout(
						FlowLayout.CENTER, 10, 10));
					final JTextField lowerBound = new JTextField(Integer
						.toString(vs.lowerBound));
					final JTextField upperBound = new JTextField(Integer
						.toString(vs.upperBound));
					diaFields.add(new JLabel("Lower bound:"));
					diaFields.add(lowerBound);
					diaFields.add(new JLabel("Upper bound:"));
					diaFields.add(upperBound);
					JPanel diaButtons = new JPanel(new FlowLayout(
						FlowLayout.CENTER, 10, 10));
					JButton okButton = new JButton("OK");
					okButton.addActionListener(new ActionListener() {
						@Override
						public void actionPerformed(ActionEvent event) {
							try {
								vs.setVitalRange(Integer.parseInt(lowerBound
									.getText()), Integer.parseInt(upperBound
									.getText()));
							}
							catch (Exception e) {
								e.printStackTrace();
							}

							rangeDialog.dispose();
						}
					});
					JButton cancelButton = new JButton("Cancel");
					cancelButton.addActionListener(new ActionListener() {

						@Override
						public void actionPerformed(ActionEvent e) {
							rangeDialog.dispose();
						}
					});
					diaButtons.add(okButton);
					diaButtons.add(cancelButton);
					rangeDialog.add(diaText, BorderLayout.NORTH);
					rangeDialog.add(diaFields, BorderLayout.CENTER);
					rangeDialog.add(diaButtons, BorderLayout.SOUTH);
					rangeDialog.setVisible(true);
				}
			});

			rangeButton.setSize(100, rangeButton.getHeight());
			row1.add(rangeButton, BorderLayout.EAST);
			measurementPanel.add(row1);
			JPanel row2 = new JPanel(new BorderLayout(5, 5));
			row2.setBorder(new EmptyBorder(3, 3, 3, 3));

			JLabel heartRateValueLabel = new JLabel();
			heartRateValueLabel
				.setFont(new Font("measureValue", Font.PLAIN, 24));
			row2.add(heartRateValueLabel, BorderLayout.WEST);
			final JCheckBox checkBox = new JCheckBox("Enabled");
			checkBox.setSelected(true);
			checkBox.addChangeListener(new ChangeListener() {

				@Override
				public void stateChanged(ChangeEvent arg0) {
					setVitalSignEnabled(vs, checkBox.isSelected());
					checkBox.setText(checkBox.isSelected() ? "Enabled"
						: "Disabled");
				}
			});
			row2.add(checkBox, BorderLayout.EAST);
			measurementPanel.add(row2);
			panel.add(measurementPanel);

			measurementLabels.put(vs.id, new JComponent[] {
				heartRateValueLabel, rangeButton, title });
		}

	}

	@Override
	public void onAlarmTriggered(VitalSign vitalSign) {
		if (isVitalSignDisabled(vitalSign))
			return;
		if (blinkerThread == null) {
			blinkerThread = new BlinkyThread();
			blinkerThread.start();
		}
	}

	@Override
	public void onNewVitalMeasurement(VitalSignMeasurement vitalSign) {
		if (isVitalSignDisabled(vitalSign.vitalSign))
			return;
		if (measurementLabels.get(vitalSign.vitalSign.id) != null) {
			((JLabel) measurementLabels.get(vitalSign.vitalSign.id)[0])
				.setText(vitalSign.toString());
		}
		if (measurementSeries.get(vitalSign.vitalSign.id) != null) {
			TimeSeries series = measurementSeries.get(vitalSign.vitalSign.id);
			series.addOrUpdate(new Second(new Date()), vitalSign.percentValue);
			series.removeAgedItems(false);
		}
	}

	private boolean isVitalSignDisabled(VitalSign vs) {
		return disabledVitalSigns.contains(vs);
	}

	private void setVitalSignEnabled(VitalSign vs, boolean enabled) {
		for (JComponent jc : measurementLabels.get(vs.id)) {
			jc.setEnabled(enabled);
		}
		if (enabled) {
			disabledVitalSigns.remove(vs);
		}
		else {
			if (!disabledVitalSigns.contains(vs))
				disabledVitalSigns.add(vs);
		}
	}

	/**
	 * A thread responsible for blinking a panel's background color
	 */
	private class BlinkyThread extends Thread {
		boolean stop = false;

		/**
		 * Stops the thread without interupting anything
		 */
		public void stopGracefully() {
			stop = true;
		}

		/**
		 * Blinks the panel every second
		 */
		@Override
		public void run() {
			while (!stop) {
				if (alarmButton.getBackground() == Color.RED) {
					alarmButton.setBackground(Color.LIGHT_GRAY);
				}
				else {
					alarmButton.setBackground(Color.RED);
				}
				try {
					Thread.sleep(1000);
				}
				catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			alarmButton.setBackground(Color.LIGHT_GRAY);
		}
	}

	/**
	 * Adds the given listener to be notified when the call button is reset
	 * 
	 * @param listener
	 *            The listener to add
	 */
	public void addCancelCallListener(CancelCallListener listener) {
		listeners.add(listener);
	}

	/**
	 * Removes the given listener
	 * 
	 * @param listener
	 *            The listener to remove
	 */
	public void removeCancelCallListener(CancelCallListener listener) {
		listeners.remove(listener);
	}

	/**
	 * A simple listener for being notified when the alarm or call button is
	 * reset
	 */
	public static interface CancelCallListener {
		void onCallCanceled();

		void onAlarmCanceled();
	}
}
